package com.yy.weixinpay.service.impl;

import com.alipay.api.domain.StaffInfo;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.yy.weixinpay.config.WxPayConfig;
import com.yy.weixinpay.entity.OrderInfo;
import com.yy.weixinpay.entity.RefundInfo;
import com.yy.weixinpay.enums.OrderStatus;
import com.yy.weixinpay.enums.PayType;
import com.yy.weixinpay.enums.wxpay.WxApiType;
import com.yy.weixinpay.enums.wxpay.WxNotifyType;
import com.yy.weixinpay.enums.wxpay.WxTradeState;
import com.yy.weixinpay.service.OrderInfoService;
import com.yy.weixinpay.service.PaymentInfoService;
import com.yy.weixinpay.service.RefundInfoService;
import com.yy.weixinpay.service.WxPayService;
import com.yy.weixinpay.utils.OrderNoUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
    @Autowired
    private WxPayConfig wxPayConfig;
    @Autowired
    private CloseableHttpClient wxPayClient;
    @Autowired
    private OrderInfoService orderInfoService;
    @Autowired
    private PaymentInfoService paymentInfoService;
    @Autowired
    private RefundInfoService refundInfoService;
    @Autowired
    private CloseableHttpClient wxPayNoSignClient;

    private final ReentrantLock  lock = new ReentrantLock();
    @Override
    public Map<String, Object> nativePay(Long productId) throws Exception {
        log.info("生成订单");
        //创建一个临时订单
//        OrderInfo orderInfo = new OrderInfo();
//        orderInfo.setTitle("测试下单");
//        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());
//        orderInfo.setProductId(productId);
//        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
//        orderInfo.setTotalFee(1);
        
        //保存订单到数据库(未支付）
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.WXPAY.getType());
        String codeUrl = orderInfo.getCodeUrl();
        if (codeUrl != null && !StringUtils.isEmpty(codeUrl)){
            Date date = new Date();
            long time = date.getTime() - orderInfo.getCreateTime().getTime();
            if (time/(3600*2*1000) < 2){
                log.info("code_url存在:{}",codeUrl);
                Map<String,Object> map2 = new HashMap<>();
                map2.put("code_url",codeUrl);
                map2.put("orderNo",orderInfo.getOrderNo());
                return map2;
            }else {
                //删除数据库对应的订单信息
                orderInfoService.removeById(orderInfo.getId());
                //重新生成订单信息
                orderInfoService.createOrderByProductId(productId,PayType.WXPAY.getType());
            }
        }
        //调用统一下单API,使用HTTPClient对象发送请求
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        //设置参数 把数据封装到map中
        Map<String,Object> map = new HashMap<>();
        map.put("appid",wxPayConfig.getAppid());
        map.put("mchid",wxPayConfig.getMchId());
        map.put("description",orderInfo.getTitle());
        map.put("out_trade_no",orderInfo.getOrderNo());
        //设置回调地址
        //微信根据这个地址，把结果返回给我们的后台系统
        map.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
//        map.put("amount",new HashMap<String,Object>(){
//            {
//                put("total", orderInfo.getTotalFee());
//                put("currency", "CNY");
//            }
//        });
        Map<String,Object> amountMap = new HashMap<>();
        amountMap.put("total",orderInfo.getTotalFee());
        amountMap.put("currency","CNY");
        map.put("amount",amountMap);

        //把map转换为json
        Gson gson = new Gson();
        String s = gson.toJson(map);
        log.info("请求参数"+s);
        //封装请求参数body
        StringEntity entity = new StringEntity(s, "UTF-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        //设置请求头
        httpPost.setHeader("Accept","application/json");
        CloseableHttpResponse response = null;
        try {
            //完成签名并执行请求到微信
            //得到一个结果，结果封装到了CloseableHttpResponse中
            response = wxPayClient.execute(httpPost);
            //解析response
            String stringAsBody = EntityUtils.toString(response.getEntity());
//        System.out.println(stringAsBody);

            //获取状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //200表示正常且有返回值
            if (statusCode == 200){
                log.info("返回值为:{}",stringAsBody);
            } else if (statusCode == 204) {//204表示正常，但是没有返回值
                log.info("请求成功，但是没有返回值");
            }else {
                log.error("请求状态码:"+statusCode+",return body"+statusCode);
            }
            //解析结果，进行数据的封装,把结果给前端
            HashMap<String,String> resultMap = gson.fromJson(stringAsBody, HashMap.class);
            //得到code_url
            String code_url = resultMap.get("code_url");
            //保存code——url到数据库
            orderInfoService.saveCodeUrl(orderInfo.getOrderNo(),code_url);
            Map<String,Object> map2 = new HashMap<>();
            map2.put("code_url",code_url);
            map2.put("orderNo",orderInfo.getOrderNo());

            return map2;
        }finally {
            response.close();
        }
    }

    /**
     * 对密文解密
     * @param map
     */
    @Override
    public void processOrder(Map<String, Object> map) {
        //处理订单
        log.info("处理订单");
        Gson gson = new Gson();
        //对数据进行解密 得到明文
        try {
            String plainText = decryptFromResource(map);
            //从明文肿拿到消息，更新数据库
            Map<String,String> plainTextMap = gson.fromJson(plainText, Map.class);
            //获取订单编号
            String out_trade_no = plainTextMap.get("out_trade_no");
            //如果订单状态已经更新，不执行更新操作
            String orderStatus = orderInfoService.getOrderStatus(out_trade_no);
            //这个锁为竞争锁，没有占有则直接放弃执行，抢占到了则执行
            if (lock.tryLock()){
                try{
                    if (!OrderStatus.NOTPAY.getType().equals(orderStatus)){
                        return;
                    }
                    //更新订单状态
                    orderInfoService.updateStatusByOrderNo(out_trade_no,OrderStatus.SUCCESS);
                    //记录支付日志
                    paymentInfoService.createPaymentInfo(plainText);
                }finally {
                    //手动释放锁，锁不会自动释放
                    lock.unlock();
                }
            }

        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 取消订单
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) {
        //调用微信的关单接口
        this.closeOrder(orderNo); //需要一个返回值 todo
        //修改本地数据库订单状态，设置为取消订单
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CANCEL);
    }

    /**
     * 用户主动查询订单
     * @param orderNo
     * @return
     */
    @Override
    public String queryOrder(String orderNo) {
        log.info("调用微信查单接口:{}",orderNo);
        //生成url
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        //设置请求头
        httpGet.setHeader("Accept","application/json");
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);
            String body = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200){
                log.info("查单成功 success,数据为：{}",body);
            }else if (statusCode == 204){
                log.info("查单成功");
            }else {
                throw new RuntimeException("查单失败");
            }
            return body;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 查询未支付的订单是否是已支付没有收到回调通知，核实订单状态
     * @param orderNo
     */
    @Override
    public void checkOrderStatus(String orderNo) {
        log.info("核实订单状态执行");
        String s = this.queryOrder(orderNo);
        log.info("订单状态为：{}",s);
        //解析数据
        Gson gson = new Gson();
        Map<String,Object> map = gson.fromJson(s, Map.class);
        //获取订单状态
        String tradeState = (String) map.get("trade_state");
        if (WxTradeState.SUCCESS.getType().equals(tradeState)){
            log.info("查询到该订单已经支付：{}",orderNo);
            //修改订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(s);
        }

        if (WxTradeState.NOTPAY.getType().equals(tradeState)){
            log.info("订单未支付：{}",orderNo);
            //如果订单未支付，则调用关单接口
            this.closeOrder(orderNo);
            //更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);
        }
    }

    /**
     * 商户退款
     * @param orderNo
     * @param reason
     */
    @Override
    public void refund(String orderNo, String reason) {
        log.info("创建退款记录");
        //根据订单编号创建退款单
        RefundInfo refundInfo = refundInfoService.createRefundByOrderNo(orderNo,reason);
        log.info("调用退款API");
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);
        //设置请求参数
        Gson gson = new Gson();
        Map<String,Object> bodyMap = new HashMap<>();
        bodyMap.put("out_refund_no",refundInfo.getRefundNo());
        Map<String,Object> amountMap = new HashMap<>();
        amountMap.put("refund",refundInfo.getRefund());
        amountMap.put("total",refundInfo.getTotalFee());
        amountMap.put("currency","CNY");
        bodyMap.put("amount",amountMap);
        bodyMap.put("reason",reason);
        bodyMap.put("out_trade_no",orderNo);
        bodyMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));
        //map->json
        String json = gson.toJson(bodyMap);
        StringEntity entity = new StringEntity(json,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept","application/json");
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
            String s = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200){
                log.info("退款成功:{}",s);
            }else if (statusCode == 204){
                log.info("成功");
            }else {
                log.info("退款异常");
                throw new RuntimeException("退款异常，响应码"+statusCode+"，响应体"+s);
            }
            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.REFUND_PROCESSING);
            //更新退款单
            refundInfoService.updateRefund(s);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 订单退款处理
     * @param bodyMap
     */
    @Override
    public void processRefund(HashMap<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("退款单");
        String plainText = decryptFromResource(bodyMap);
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String outTradeNo = (String) plainTextMap.get("out_trade_no");
        if (lock.tryLock()){
            try {
                String orderStatus = orderInfoService.getOrderStatus(outTradeNo);
                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)){
                    return;
                }
                //退款完成，更新订单状态
                orderInfoService.updateStatusByOrderNo(outTradeNo,OrderStatus.REFUND_SUCCESS);
                //更新退款单
                refundInfoService.updateRefund(plainText);
            }finally {
                lock.unlock();
            }
        }
    }

    /**
     * 查询退款
     * @param refundNo
     * @return
     */
    @Override
    public String queryRefund(String refundNo) {
        log.info("调用查询退款API：{}",refundNo);
        String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(),refundNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept","application/json");
        //完成签名并且发送请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);
            String body = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200){
                log.info("有参响应成功:{}",body);
            }else if (statusCode == 204){
                log.info("无参响应成功");
            }else {
                throw new RuntimeException("查询退款异常");
            }
            return body;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 获取账单
     * @param billDate
     * @param type
     * @return
     */
    @Override
    public String queryBill(String billDate, String type) {
        log.info("获取账单接口:{}",billDate);
        String url = "";
        if ("tradebill".equals(type)){
            url = WxApiType.TRADE_BILLS.getType();
        } else if ("fundflowbill".equals(type)) {
            url = WxApiType.FUND_FLOW_BILLS.getType();
        }else {
            throw new RuntimeException("不支持的账单类型");
        }
        url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept","application/json");
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);
            String body = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200){
                log.info("查询订单响应成功:{}",body);
            }else if (statusCode == 204){
                log.info("响应成功，无参返回方法");
            }else {
                throw new RuntimeException("响应失败,返回码"+statusCode);
            }
            Gson gson = new Gson();
            HashMap<String,String> hashMap = gson.fromJson(body, HashMap.class);
            return hashMap.get("download_url");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 下载账单
     * @param billDate
     * @param type
     * @return
     */
    @Override
    public String downloadBill(String billDate, String type) {
        log.info("下载账单接口调用{},{}",billDate,type);
        //获取账单url
        String url = this.queryBill(billDate,type);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept","application/json");
        CloseableHttpResponse response = null;
        try {
            response = wxPayNoSignClient.execute(httpGet);
            String body = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200){
                log.info("成功,下载订单返回结果=="+body);
            }else if (statusCode == 204){
                log.info("成功");
            }else {
                throw new RuntimeException("失败");
            }
            return body;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 关闭订单接口
     * @param orderNo
     */
    private void closeOrder(String orderNo) {
        log.info("调用微信支付的关单接口，订单号为：{}",orderNo);
        //拼接url
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(),orderNo);
        url = wxPayConfig.getDomain().concat(url);

        //创建对象发送请求
        HttpPost httpPost = new HttpPost(url);
        //封装数据
        Gson gson = new Gson();
        Map<String,String> paramMap = new HashMap<>();
        //需要商户号，商户订单号已经拼接到了路径
        paramMap.put("mchid",wxPayConfig.getMchId());
        String s = gson.toJson(paramMap);
        log.info("请求参数：{}",s);
        //封装请求参数
        StringEntity entity = new StringEntity(s,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept","application/json");
        CloseableHttpResponse response = null;
        try {
            //发送请求获得响应
            response = wxPayClient.execute(httpPost);
            //拿到响应码
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200){//有返回值
                log.info("success:200");
            }else if (statusCode == 204){
                log.info("处理成功，没有返回值");
            }else {
                log.info("关单失败");
                throw new RuntimeException("request failed");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                response.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private String decryptFromResource(Map<String, Object> map) throws GeneralSecurityException {
        //得到resource
        Map<String,String> resource = (Map<String, String>) map.get("resource");
        //获取密文
        String ciphertext = resource.get("ciphertext");
        //得到随机串  盐值加密的
        String nonce = resource.get("nonce");
        //附加数据包
        String associated_data = resource.get("associated_data");

        //解密 传入商户的apiV3Key
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        //根绝随机串进行解密
        String plainTex = aesUtil.decryptToString(associated_data.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        log.info("解密后的明文：{}",plainTex);
        return plainTex;
    }


}
